package org.syntaxlisp.core.element;

import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

@Data
public abstract class SyntaxLispElement {

    private static final AtomicInteger ID_GENERATOR = new AtomicInteger(1);

    private final int id = ID_GENERATOR.getAndIncrement();

    private int layer;

    private final SyntaxLispElementType type;

    /**
     * Reference/Base 的 name 以及 String 的 value
     */
    private final String val;

    private String belongsToDef;

    protected List<SyntaxLispElement> subElements;

    private PfCr basePfCr;

    private PfCr basePlCl;

    private PfCr directPfCr;

    private PfCr directPlCl;

    /**
     * Direct Possible Contains and Critical Contains
     */
    private PfCr directPcCc;

    /**
     * Base Possible Contains and Critical Contains
     */
    private PfCr basePcCc;

    private SyntaxLispElement upper;

    /**
     * 下一个可到达的同级 sub elements，只在 seq connect 中有效
     */
    private List<SyntaxLispElement> directNext;

    private boolean canSkipToUpperNext;

    private boolean hunger;

    private boolean matchButIgnore;

    private Set<SyntaxLispElement> lookAheadExcludes;

    private boolean reduceAsString;

    private List<Set<SyntaxLispElement>> directPriorityPfList;

    private List<Map<SyntaxLispElement, Set<SyntaxLispElement>>> basePriorityPfList;

    private List<Set<SyntaxLispElement>> directPriorityNextList;

    private List<Map<SyntaxLispElement, Set<SyntaxLispElement>>> basePriorityNextList;

    protected SyntaxLispElement (SyntaxLispElementType type, String val) {
        Objects.requireNonNull(type, "Type can not be null.");
        this.type = type;
        this.val = val;
        this.subElements = Collections.emptyList();
    }

    public Set<SyntaxLispElement> getBasePf () {
        return basePfCr.getPossibleSet();
    }

    public Set<SyntaxLispElement> getBaseCr () {
        return basePfCr.getCriticalSet();
    }

    public Set<SyntaxLispElement> getDirectPf () {
        return directPfCr.getPossibleSet();
    }

    public Set<SyntaxLispElement> getDirectPl () {
        return directPlCl.getPossibleSet();
    }

    public Set<SyntaxLispElement> getDirectCr () {
        return directPfCr.getCriticalSet();
    }

    public Set<SyntaxLispElement> getDirectPc () {
        return directPcCc.getPossibleSet();
    }

    public Set<SyntaxLispElement> getBasePc () {
        return basePcCc.getPossibleSet();
    }

    public SyntaxLispElement getSub (int index) {
        return subElements.get(index);
    }

    public void setSub (int index, SyntaxLispElement sub) {
        subElements.set(index, sub);
    }

    public int getSubsSize() {
        return subElements.size();
    }

    public boolean isCritical () {
        return !CollectionUtils.isEmpty(basePfCr.getCriticalSet());
    }

    public String toString () {
        return "[" + val + "-" + type + "-" + getBelongsToDef() + "]";
    }

    public String toErrMsgString () {
        return "[" + belongsToDef + "," + val + "," + type + "]";
    }

    public int hashCode () {
        return id;
    }

    public abstract SyntaxLispElement copy ();

    @Override
    public boolean equals(Object o) {
//        return (o instanceof SyntaxLispElement)
//                && type == ((SyntaxLispElement) o).getType()
//                && equalsTo((SyntaxLispElement) o);
        return (o instanceof SyntaxLispElement)
                && id == ((SyntaxLispElement) o).getId();
    }

    public abstract boolean equalsTo(SyntaxLispElement o);

    public SyntaxLispElement copyMatchPropertiesAndReturn(SyntaxLispElement src) {
        setHunger(src.isHunger());
        setLookAheadExcludes(src.getLookAheadExcludes());
        setMatchButIgnore(src.isMatchButIgnore());
        setReduceAsString(src.isReduceAsString());
        return this;
    }

    public boolean isRef () {
        return SyntaxLispElementType.REF.isTypeMatch(this);
    }

    public boolean isBase () {
        return SyntaxLispElementType.BASE.isTypeMatch(this);
    }

    public boolean isRepeat () {
        return SyntaxLispElementType.SIMPLIFY_REPEAT.isTypeMatch(this);
    }
}
